SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
3.8 KB · 85 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { DayStrip, LargestGrid, TopEvents, TopLists } from '@/components/history/HistoryViews';5import { Section, Stat } from '@/components/ui/primitives';6import { apiGet } from '@/lib/api';7import { MONTH_NAMES, fmt, fmtInt } from '@/lib/format';8import type { HistorySummary } from '@/lib/types';910export const dynamic = 'force-dynamic';1112type Params = Promise<{ year: string; month: string }>;1314export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {15  const { year, month } = await params;16  const name = MONTH_NAMES[Number(month) - 1] ?? month;17  return { title: `History ${name} ${year}`, description: `Daily Global Internet Pressure for ${name} ${year} with the largest events and most affected networks.` };18}1920export default async function HistoryMonthPage({ params }: { params: Params }) {21  const { year, month } = await params;22  const y = Number(year);23  const m = Number(month);24  if (!/^\d{4}$/.test(year) || !(m >= 1 && m <= 12)) notFound();25  const s = await apiGet<HistorySummary>(`/api/v1/history/summary?year=${y}&month=${m}`);26  const days = s.days ?? [];27  const max = days.length ? Math.max(...days.map((d) => d.max)) : null;28  const min = days.length ? Math.min(...days.map((d) => d.min)) : null;29  const avg = days.length ? days.reduce((a, d) => a + d.avg, 0) / days.length : null;30  const events = days.reduce((a, d) => a + d.events, 0);31  const idx = s.available_months.indexOf(`${year}-${String(m).padStart(2, '0')}`);32  const prev = idx > 0 ? s.available_months[idx - 1] : null;33  const next = idx >= 0 && idx < s.available_months.length - 1 ? s.available_months[idx + 1] : null;34  const toHref = (ym: string) => `/history/${ym.split('-')[0]}/${Number(ym.split('-')[1])}`;35  return (36    <div className="pb-8">37      <header className="pt-6 pb-4">38        <p className="label">39          <Link href="/history" className="hover:text-ink">40            History41          </Link>{' '}42          ·{' '}43          <Link href={`/history/${year}`} className="hover:text-ink">44            {year}45          </Link>46        </p>47        <div className="mt-1 flex flex-wrap items-baseline gap-4">48          <h1 className="text-[26px] font-medium tracking-tight sm:text-[32px]">49            {MONTH_NAMES[m - 1]} <span className="num">{year}</span>50          </h1>51          <nav className="num flex gap-2 text-[12px]" aria-label="Adjacent months">52            {prev && (53              <Link href={toHref(prev)} className="text-ink-2 hover:text-ink">54                ← {prev}55              </Link>56            )}57            {next && (58              <Link href={toHref(next)} className="text-ink-2 hover:text-ink">59                {next} →60              </Link>61            )}62          </nav>63        </div>64        <div className="mt-4 grid grid-cols-2 gap-4 sm:grid-cols-5">65          <Stat label="days observed" value={fmtInt(days.length)} />66          <Stat label="max" value={<span style={{ color: max == null ? undefined : `var(--p-${max <= 10 ? 'calm' : max <= 25 ? 'normal' : max <= 40 ? 'elevated' : max <= 55 ? 'stressed' : max <= 70 ? 'high' : max <= 85 ? 'severe' : 'extreme'})` }}>{fmt(max)}</span>} />67          <Stat label="avg" value={fmt(avg)} />68          <Stat label="min" value={fmt(min)} />69          <Stat label="events" value={fmtInt(events)} />70        </div>71      </header>72      <Section label="Calendar" right={<span>daily max pressure</span>}>73        <DayStrip days={days} year={y} month={m} />74      </Section>75      <Section label="Largest events">76        <LargestGrid largest={s.largest} />77      </Section>78      <TopLists s={s} />79      <Section label="Events this month">80        <TopEvents events={s.top_events.filter((e) => e.started_at.startsWith(`${year}-${String(m).padStart(2, '0')}`))} />81      </Section>82    </div>83  );84}85